package com.adwyxx.collection;

import java.io.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;

/**
 * {@link Map} 接口:
 * <pre>
 * Map 是一个双列集合，其中每个元素都由一个键和一个值组成，键和值都是对象，键对象不可以重复，值对象可以重复。
 * Map 接口有多个实现类，常用的有：HashMap、LinkedHashMap、TreeMap、Hashtable、Properties 等。
 * </pre>
 * Map 接口中的常用方法：
 * <pre>
 * + put(Object key, Object value)：向 Map 中添加元素，key 为键，value 为值。
 * + get(Object key)：根据键获取值。
 * + remove(Object key)：根据键删除键值对。
 * + containsKey(Object key)：判断 Map 中是否包含指定的键。
 * + containsValue(Object value)：判断 Map 中是否包含指定的值。
 * + size()：获取 Map 中的键值对数量。
 * + isEmpty()：判断 Map 是否为空。
 * + keySet()：获取 Map 中所有的键，返回一个 Set 集合。
 * + values()：获取 Map 中所有的值，返回一个 Collection 集合。
 * + entrySet()：获取 Map 中所有的键值对，返回一个 Set 集合，集合中的每个元素都是 Map.Entry 对象。
 * + clear()：清空 Map 中的所有键值对。
 * </pre>
 *
 * @author Leo.W
 * @date 2024-09-18 9:08
 */
public class MapDemo {

    /**
     * 自定义一个类，用作 Map 的键。
     * Map 中的 key用Set来存放，不允许重复，即同一个 Map 对象所对应的类，须重写hashCode()和equals()方法
     */
    class MyKey implements Comparable<MyKey> {
        private String name;
        private int index;

        public MyKey(String name, int index) {
            this.name = name;
            this.index = index;
        }

        @Override
        public int hashCode() {
            return this.name.hashCode()+this.index;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof MyKey) {
                MyKey key = (MyKey) obj;
                return this.name.equals(key.name) && this.index == key.index;
            }
            return false;
        }

        @Override
        public String toString() {
            return "MyKey{"+this.name + ":" + this.index+"}";
        }

        /**
         * TreeMap 的 Key 的排序：
         * 自然排序：TreeMap 的所有的 Key 必须实现 Comparable 接口，而且所有的 Key 应该是同一个类的对象，否则将会抛出 ClasssCastException
         * 定制排序：创建 TreeMap 时，构造器传入一个 Comparator 对象，该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口
         * @param o the object to be compared.
         * @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object.
         */
        @Override
        public int compareTo(MyKey o) {
            return this.index - o.index;
        }
    }

    public MyKey createMyKey(String name, int index){
        return new MyKey(name,index);
    }

    public static void main(String[] args) {
        /**
         * HashMap
         * HashMap 是 Map 接口使用频率最高的实现类。
         * HashMap 是线程不安全的。允许添加 null 键和 null 值。
         * 存储数据采用的哈希表结构，底层使用一维数组+单向链表+红黑树进行key-value数据的存储。与HashSet一样，元素的存取顺序不能保证一致。
         * HashMap 判断两个key相等的标准是：两个 key 的hashCode值相等，通过 equals() 方法返回 true。
         * HashMap 判断两个value相等的标准是：两个 value 通过 equals() 方法返回 true。
         */
        Map<String, Integer> map = new HashMap<>();
        map.put("a", 1);
        map.put("b", 2);
        map.put("c", 3);
        map.put("d", 4);
        map.put("e", 5);
        System.out.println(map);
        // 获取键为 "a" 的值
        System.out.println("map.get(\"a\") -> "+ map.get("a"));
        // 删除键为 "b" 的键值对
        map.remove("b");
        // 判断 Map 中是否包含键为 "c" 的键值对
        System.out.println("map.containsKey(\"c\") -> "+ map.containsKey("c"));
        // 判断 Map 中是否包含值为 5 的键值对
        System.out.println("map.containsValue(5) -> "+ map.containsValue(5));
        // 获取 Map 中的键值对数量
        System.out.println("map.size() -> "+ map.size());

        // 使用 Entry<> 遍历 Map
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey() + " : " + entry.getValue());
        }
        // 使用迭代器遍历 Map：遍历的元素对象是 Entry
        Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Integer> entry = iterator.next();
            System.out.println(entry.getKey() + " : " + entry.getValue());
        }

        // 使用 keySet() 遍历 Map
        for (String key : map.keySet()) {
            System.out.println(key + " : " + map.get(key));
        }

        // 使用 values() 遍历 Map
        for (Integer value : map.values()) {
            System.out.println(value);
        }

        // 清空 Map 中的所有键值对
        map.clear();

        // 判断 Map 是否为空
        System.out.println("map.isEmpty() -> "+ map.isEmpty());

        // 获取指定key对应的value,如果不存在，则返回设置的默认值
        System.out.println(map.getOrDefault("key", -1));

        Map<MyKey, String> map2 = new HashMap<>();
        MapDemo  mapDemo = new MapDemo();
        map2.put(mapDemo.createMyKey("key1",1),"value1");
        map2.put(mapDemo.createMyKey("key2",2),"value2");
        map2.put(mapDemo.createMyKey("key3",3),"value3");
        System.out.println(map2);

        /**
         * LinkedHashMap
         * LinkedHashMap 是 HashMap 的子类
         * 存储数据采用的哈希表结构+链表结构，在HashMap存储结构的基础上，使用了一对双向链表来记录添加元素的先后顺序，可以保证遍历元素时，与添加的顺序一致。
         * 通过哈希表结构可以保证键的唯一、不重复，需要键所在类重写hashCode()方法、equals()方法。
         */
        LinkedHashMap<String, Integer> linkedHashMap = new LinkedHashMap<>();
        linkedHashMap.put("a", 1);
        linkedHashMap.put("b", 2);
        linkedHashMap.put("c", 3);
        System.out.println(linkedHashMap); // {a=1, b=2, c=3}
        // 可以看出 LinkedHashMap 的遍历顺序和添加顺序一致
        for (Map.Entry<String, Integer> entry : linkedHashMap.entrySet()) {
            System.out.println(entry.getKey() + " : " + entry.getValue());
        }

        /**
         * TreeMap 可以保证所有的 key-value 对处于有序状态。
         * TreeSet底层使用红黑树结构存储数据
         * TreeMap 的 Key 的排序：
         *  自然排序：TreeMap 的所有的 Key 必须实现 Comparable 接口，而且所有的 Key 应该是同一个类的对象，否则将会抛出 ClasssCastException
         *  定制排序：创建 TreeMap 时，构造器传入一个 Comparator 对象，该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口
         * TreeMap判断两个key相等的标准：两个key通过compareTo()方法或者compare()方法返回0。
         */
        TreeMap<String, Integer> treeMap = new TreeMap<>();
        treeMap.put("c", 3);
        treeMap.put("a", 1);
        treeMap.put("b", 2);
        System.out.println(treeMap); // {a=1, b=2, c=3}

        TreeMap<MyKey, String> treeMap2 = new TreeMap<>();
        treeMap2.put(mapDemo.createMyKey("key2",2),"value1");
        treeMap2.put(mapDemo.createMyKey("key3",3),"value2");
        treeMap2.put(mapDemo.createMyKey("key1",1),"value3");
        System.out.println(treeMap2); // {MyKey{key1:1}=value3, MyKey{key2:2}=value1, MyKey{key3:3}=value2}


        /**
         * HashTable 线程安全的 Map
         * HashTable 和 HashMap 的实现原理几乎一样，差别在于：
         * HashTable 中的方法是同步的，而 HashMap 不是同步的。因此，HashTable 线程安全，HashMap 线程不安全。
         * HashTable 不允许使用 null 作为 key 或者 value，而 HashMap 可以。
         * HashTable 计算哈希值的方法直接使用对象的 hashCode() 方法，而 HashMap 重新定义了计算哈希值的方法，性能可能更好。
         * HashTable 是遗留类，不建议在新的代码中使用，不需要线程安全的场合可以用 HashMap 替换，需要线程安全的场合可以使用 ConcurrentHashMap。
         *
         * ConcurrentHashMap:
         * ConcurrentHashMap 是 Java 7 中提供的线程安全且高效的 HashMap 实现。它是 HashTable 的替代品，但不能完全替代 HashTable。
         * ConcurrentHashMap 和 HashMap 的实现方式几乎一样，最主要的区别是 ConcurrentHashMap 采用了分段锁（Segment），每个分段锁维护着几个桶（HashEntry），这样只要不是哈希冲突，就无需加锁。因此，理论上 ConcurrentHashMap 可以支持接近 16 个线程的并发。
         * ConcurrentHashMap 在 Java 8 中进行了进一步的优化，使用了一种叫做 CAS 的操作来保证并发安全性，同时使用了红黑树来处理哈希冲突。
         *
         * CAS（Compare-And-Swap）:
         * CAS 是一种原子操作，它可以在多线程环境下保证数据的一致性。CAS 操作包含三个操作数：内存位置（V）、预期原值（A）和新值（B）。
         *  - 如果内存位置的值与预期原值相等，则将内存位置的值更新为新值。
         *  - 否则，不做任何操作。
         * CAS 操作是一种乐观锁，它假设在并发情况下，大多数操作都不会发生冲突，因此它不会像悲观锁那样在每次操作前都加锁，而是通过比较和替换的方式来保证数据的一致性。
         */
        CountDownLatch latch = new CountDownLatch(5);
        HashMap<String, String> hashMap = new HashMap<>();
        Hashtable<String, String> hashtable = new Hashtable<>();
        ConcurrentHashMap<String, String> concurrentHashMap = new ConcurrentHashMap<>();
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                for (int j = 0; j < 5; j++){
                    hashMap.put("key" + j, "value" + j);
                    hashtable.put("key" + j, "value" + j);
                    concurrentHashMap.put("key" + j, "value" + j);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                latch.countDown();
            },"thread-"+i).start();
        }
        try {
            latch.await();
            System.out.println(hashMap);
            System.out.println(hashtable);
            System.out.println(concurrentHashMap);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        /**
         * Properties:
         * Properties 类是 Hashtable 的子类，它继承自 Hashtable，并且扩展了 Hashtable 的功能，使其能够读取和写入属性文件。
         * Properties 类通常用于读取和写入配置文件，例如应用程序的配置文件。
         * 由于属性文件里的 key、value 都是字符串类型，所以 Properties 中要求 key 和 value 都是字符串类型
         * Properties 类提供了一些方法:
         *  - getProperty(String key)：获取属性值
         *  - setProperty(String key, String value)：设置属性值
         *  - store(OutputStream out, String comments)：写入属性文件。
         *  - list(PrintStream out) 和 list(PrintWriter out)：用于将属性列表输出到指定的输出流中。
         */
        try {
            Properties properties = new Properties();
            // 1、创建一个原始的字节输入流
            FileInputStream stream =  new FileInputStream("src/com/adwyxx/collection/user.properties");
            // 2、把原始的字节输入流按照指定的字符集编码转换成字符输入流
            Reader reader = new InputStreamReader(stream, "UTF-8");
            properties.load(reader);
            System.out.println(properties);
            System.out.println(properties.getProperty("name"));
            // 修改属性
            properties.setProperty("name", "adwyxx");
            // 保存到文件
            properties.store(new FileWriter("src/com/adwyxx/collection/user.properties"), "update");
            // 将属性列表输出到指定的输出流中
            properties.list(System.out);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }
}
